/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
 * File Name     : roce_bond_register.c
 * Version       : v2.0
 * Created       : 2021/12/7
 * Last Modified : 2022/3/24
 * Description   : The definition of RoCE bond register related functions prototype.
 */

#ifdef ROCE_BONDING_EN

#include <rdma/ib_user_verbs.h>
#include <rdma/ib_addr.h>
#include <rdma/ib_cache.h>

#include <net/bonding.h>

#include "hinic3_hw.h"
#include "hinic3_srv_nic.h"

#include "roce_bond.h"
#include "roce_cmd.h"
#include "securec.h"

static DEFINE_MUTEX(roce3_intf_mutex);

static void roce3_queue_bond_work(struct roce3_bond_device *bond_dev, unsigned long delay)
{
    schedule_delayed_work(&bond_dev->bond_work, delay);
}

static bool roce3_bond_netdev_is_own_bond_dev(const struct net_device *net_dev,
    const struct roce3_bond_device *bond_dev)
{
    int func_idx;

    for (func_idx = 0; func_idx < ROCE_BOND_MAX_FUNCS; func_idx++) {
        if (net_dev == bond_dev->bond_func[func_idx].netdev) {
            return true;
        }
    }

    return false;
}

static roce3_bond_service_func g_roce3_bond_proc[] = {
    roce3_before_bond_active,
    roce3_after_bond_active,
    roce3_before_bond_modify,
    roce3_after_bond_modify,
    roce3_before_bond_deactive,
    roce3_after_bond_deactive,
};

static int roce3_check_if_need_do_bond(struct roce3_bond_device *bond_dev, struct net_device *upper_dev)
{
    struct bonding *new_bond = NULL;

    if (bond_dev->bond == NULL) {
        return 0;
    }

    new_bond = netdev_priv(upper_dev);
    if (strcmp(new_bond->dev->name, bond_dev->name) == 0) {
        return 1;
    }

    roce3_bond_detach(bond_dev);

    return 0;
}

static void roce3_set_attr(struct roce3_bond_device *bond_dev, u16 index,
    const struct netdev_lag_lower_state_info *bond_lower_info)
{
    int ret;
    struct hinic3_bond_cmd cmd = {0};
    u16 out_size = sizeof(cmd);
    struct roce3_device *rdev = NULL;

    rdev = bond_dev->rdev;
    if (rdev == NULL) {
        return;
    }

    cmd.attr = bond_dev->bond_attr;
    if (bond_lower_info->tx_enabled == 1) {
        BITMAP_SET(cmd.attr.active_slaves, index);
    } else {
        BITMAP_CLR(cmd.attr.active_slaves, index);
    }

    ret = hinic3_msg_to_mgmt_sync(bond_dev->bond_func[cmd.attr.first_roce_func].hwdev, HINIC3_MOD_OVS,
        MPU_CMD_BOND_SET_ATTR, &cmd, sizeof(cmd), &cmd, &out_size, 0, HINIC3_CHANNEL_NIC);
    if ((ret != 0) || (out_size == 0) || (cmd.ret_status != 0)) {
        pr_err("[ROCE, ERR] Failed to set bond attr, ret : %d, sts: %u, out size: %u\n",
               ret, cmd.ret_status, out_size);
    }
}

static void roce3_bond_handle_lowerstate_event(struct roce3_bond_device *bond_dev, struct net_device *net_dev,
    struct netdev_notifier_changelowerstate_info *info)
{
    struct netdev_lag_lower_state_info *bond_lower_info = NULL;
    u16 func_idx;

    if (!netif_is_lag_port(net_dev)) {
        pr_info("[ROCE, INFO] %s: Lower netdev is not belong bond.\n", __func__);
        return;
    }

    if (!(netif_running(net_dev) && netif_carrier_ok(net_dev))) {
        pr_info("[ROCE, INFO] %s: Device is not up\n", __func__);
        return;
    }

    if (bond_dev->bond_attr.bond_mode != BOND_MODE_8023AD) {
        pr_info("[ROCE, INFO] %s: Bond mode is not lacp\n", __func__);
        return;
    }

    roce3_bond_for_each_func(func_idx, 0, bond_dev) {
        if (bond_dev->bond_func[func_idx].netdev == net_dev) {
            break;
        }
    }

    if (func_idx == ROCE_BOND_MAX_FUNCS) {
        pr_info("[ROCE, INFO] %s: Net_dev is not belong with roce_bond\n", __func__);
        return;
    }

    bond_lower_info = info->lower_state_info;

    if (bond_dev->bond_tracker.netdev_state[func_idx].tx_enabled == 1) {
        roce3_set_attr(bond_dev, func_idx, bond_lower_info);
    }

    return;
}

int roce3_bond_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
{
    struct net_device *net_dev = NULL;
    struct roce3_bond_device *bond_dev = NULL;
    struct netdev_notifier_changeupper_info *info = NULL;
    int ret;
    struct net_device *upper_netdev = NULL;

    info = (struct netdev_notifier_changeupper_info *)ptr;
    net_dev = netdev_notifier_info_to_dev(ptr);
    if (net_eq(dev_net(net_dev), &init_net) == 0) {
        return NOTIFY_DONE;
    }

    if ((event != NETDEV_CHANGEUPPER) && (event != NETDEV_CHANGELOWERSTATE)) {
        return NOTIFY_DONE;
    }

    bond_dev = container_of(this, struct roce3_bond_device, nb);
    if (roce3_bond_netdev_is_own_bond_dev(net_dev, bond_dev) == false) {
        pr_err("[ROCE, INFO] %s: The event netdev(%s) doesn't belong to bond dev\n", __func__, net_dev->name);
        return NOTIFY_DONE;
    }

    if (event == NETDEV_CHANGELOWERSTATE) {
        roce3_bond_handle_lowerstate_event(bond_dev, net_dev, ptr);
        return NOTIFY_DONE;
    }

    upper_netdev = info->upper_dev;
    if (upper_netdev == NULL) {
        pr_err("[ROCE, INFO] %s: Upper dev not exist\n", __func__);
        return NOTIFY_DONE;
    }

    if (!netif_is_lag_master(upper_netdev)) {
        pr_err("[ROCE, INFO] %s: Upper dev is not master\n", __func__);
        return NOTIFY_DONE;
    }

    ret = roce3_check_if_need_do_bond(bond_dev, upper_netdev);
    if (ret != 0) {
        pr_err("[ROCE, INFO] %s: There is no need to do bond\n", __func__);
        return NOTIFY_DONE;
    }

    roce3_bond_hca_mutex_lock();
    bond_dev->bond = netdev_priv(upper_netdev);
    ret = strncpy_s(bond_dev->name, IFNAMSIZ, bond_dev->bond->dev->name, strlen(bond_dev->bond->dev->name));
    if (ret != 0) {
        pr_err("[ROCE, ERR] %s: Failed to strncpy_s\n", __func__);
        roce3_bond_hca_mutex_unlock();
        return NOTIFY_BAD;
    }
    roce3_bond_hca_mutex_unlock();

    roce3_queue_bond_work(bond_dev, 0);

    return NOTIFY_DONE;
}

static void roce3_bond_service_proc(const char *bond_name, void *bond_attr, enum bond_service_proc_pos pos)
{
    struct roce3_bond_device *bond_dev = NULL;
    struct bond_attr *attr = (struct bond_attr *)bond_attr;

    if (bond_name == NULL) {
        pr_err("[ROCE, ERR] %s: Bond_name is NULL\n", __func__);
        return;
    }

    bond_dev = roce3_find_bond_dev_by_bond_name(bond_name);
    if ((bond_dev == NULL) || (bond_dev->bond_attach == 0)) {
        pr_err("[ROCE, ERR] %s: Filed to find bond_dev by name\n", __func__);
        return;
    }

    if (pos >= BOND_POS_MAX) {
        pr_err("[ROCE, ERR] %s: The pos is out of the range of proc_func\n", __func__);
        return;
    }

    if (g_roce3_bond_proc[pos] != NULL) {
        g_roce3_bond_proc[pos](bond_name, attr);
    }
}

static void roce3_do_bond(struct roce3_bond_device *bond_dev)
{
    char *name = bond_dev->bond->dev->name;
    u16 bond_id = 0;
    int ret;

    if (bond_dev->rdev != NULL) {
        if ((bond_dev->rdev->sdi_bond_name != NULL) && (strcmp(name, bond_dev->rdev->sdi_bond_name) != 0)) {
            return;
        }
    }

    bond_dev->bond_attach = 1;

    ret = hinic3_bond_register_service_func(HINIC3_BOND_USER_ROCE, roce3_bond_service_proc);
    if (ret != 0) {
        pr_err("[ROCE, ERR] %s: Failed to register bond(%d)\n", __func__, ret);
        return;
    }

    if (bond_dev->rec_hinic_bond_id != 0) {
        bond_id = bond_dev->rec_hinic_bond_id;
        ret = hinic3_bond_detach(bond_id, HINIC3_BOND_USER_ROCE);
        if (ret != 0) {
            pr_err("[ROCE, ERR] %s: Failed to detach bond(%d)\n", __func__, bond_id);
            goto bond_unregister;
        }
        bond_dev->rec_hinic_bond_id = 0;
    }

    ret = hinic3_bond_attach(name, HINIC3_BOND_USER_ROCE, &bond_id);
    if (ret != 0) {
        pr_err("[ROCE, ERR] %s: Failed to attach bond(%d)\n", __func__, ret);
        goto bond_unregister;
    }

    bond_dev->rec_hinic_bond_id = bond_id;

    return;

bond_unregister:
    bond_dev->bond_attach = 0;
    ret = hinic3_bond_unregister_service_func(HINIC3_BOND_USER_ROCE);
    if (ret != 0) {
        pr_err("[ROCE, ERR] %s: Failed to unregister bond service func, ret(%d)\n", __func__, ret);
    }
    return;
}

static void roce3_do_bond_work(struct work_struct *work)
{
    struct delayed_work *delayed_work = to_delayed_work(work);
    struct roce3_bond_device *bond_dev = container_of(delayed_work, struct roce3_bond_device, bond_work);
    int status;

    status = mutex_trylock(&roce3_intf_mutex);
    if (status == 0) {
        /* delay 1 sec */
        roce3_queue_bond_work(bond_dev, HZ);
        return;
    }

    roce3_do_bond(bond_dev);

    mutex_unlock(&roce3_intf_mutex);
}

/* Allocate bond_dev, hook the handler */
struct roce3_bond_device *roce3_bond_dev_alloc(void)
{
    struct roce3_bond_device *bond_dev = NULL;

    bond_dev = kzalloc(sizeof(*bond_dev), GFP_KERNEL);
    if (bond_dev == NULL) {
        pr_err("[ROCE, ERR] %s: Failed to alloc bond_dev\n", __func__);
        return NULL;
    }

    INIT_DELAYED_WORK(&(bond_dev->bond_work), roce3_do_bond_work);

    return bond_dev;
}

int roce3_bond_register(struct roce3_device *rdev)
{
    int ret;

    roce3_bond_hca_mutex_lock();
    if (!roce3_bond_res_has_alloced()) {
        ret = roce3_bond_add(rdev);
        if (ret != 0) {
            roce3_bond_hca_mutex_unlock();
            dev_err(rdev->hwdev_hdl, "[ROCE, ERR] %s: Failed to bond add, func_id(%d)\n", __func__, rdev->glb_func_id);
            return -1;
        }

        roce3_bond_hca_update_info();
    }
    roce3_bond_hca_mutex_unlock();

    return 0;
}

#endif
